//*************************************************************************************
// EGTB Generator Compression Routines
//*************************************************************************************

#pragma once

#include "Compress.h"

// templated functions
namespace EndGameDB
{
	 // Function initializes compression of one database file
	////////////////////////////////////////////////////////////////////////////

	template<int BitsPerRow, int MaxRank, int MaxField> 
	void InitDbCompression(int pieceCount, FILE* wdlFile, FILE* dtmFile)
	{	
		// clear headers
		memset(&mainHeaderWDL, 0, sizeof(EGTBMainHeader));
		memset(&mainHeaderDTM, 0, sizeof(EGTBMainHeader));

		// set primary header info
		strncpy(&mainHeaderWDL.szHeaderTag[0], szDBHeaderTag, EGTB_HEADER_TAG_SIZE);
		strncpy(&mainHeaderDTM.szHeaderTag[0], szDBHeaderTag, EGTB_HEADER_TAG_SIZE);
		mainHeaderWDL.nDraughtsType = EnumToPDNCode(g_eCompressionCheckersType);
		mainHeaderDTM.nDraughtsType = EnumToPDNCode(g_eCompressionCheckersType);
		mainHeaderWDL.nCompressionType = g_nCompressionType;
		mainHeaderDTM.nCompressionType = g_nCompressionType;

		// calculate disk space and number of subdatabase slices
		uint64 nWDLSpace = 0;
		uint64 nDTMSpace = 0;
		uint64 nDiskSpace;
		int32 nSubDbCount = GetSubDbCount<BitsPerRow, MaxRank, MaxField>(pieceCount, &nDiskSpace, NULL,
			&nWDLSpace, &nDTMSpace, true);

		// allocate space for subdatabases
		pWDLSubDbs = new EGTBSubDatabase[nSubDbCount];
		pDTMSubDbs= new EGTBSubDatabase[nSubDbCount];
		memset(pWDLSubDbs, 0, sizeof(EGTBSubDatabase) * nSubDbCount);
		memset(pDTMSubDbs, 0, sizeof(EGTBSubDatabase) * nSubDbCount);

		// calculate total number of blocks
		nWDLTotalBlockCount = (nWDLSpace / g_nPageSize) + 1;
		nDTMTotalBlockCount = (nDTMSpace / g_nPageSize) + 1;
		g_nSubDbCount = nSubDbCount;
		g_nSubDbIndex = 0;
		
		// update main headers
		mainHeaderWDL.nSubDbCount = nSubDbCount;
		mainHeaderDTM.nSubDbCount = nSubDbCount;
		mainHeaderWDL.nPagingDataOffset = sizeof(EGTBMainHeader) + nSubDbCount * sizeof(EGTBSubDatabase);
		mainHeaderDTM.nPagingDataOffset = sizeof(EGTBMainHeader) + nSubDbCount * sizeof(EGTBSubDatabase);

		// write empty space for header and subdb headers
		uint64 size = sizeof(EGTBMainHeader) + nSubDbCount * sizeof(EGTBSubDatabase);
		uint8* pTemp = new uint8[size];
		memset(pTemp, 0, size);
		fwrite(pTemp, (size_t)size, (size_t)1 , wdlFile);
		fwrite(pTemp, (size_t)size, (size_t)1 , dtmFile);
		delete[] pTemp;

		// create paging managers
		g_pWDLPageManager = new PagingManager(0);
		g_pDTMPageManager = new PagingManager(0);

		// initialize compression stream
		g_pWDLPageManager->InitializeCompressionMode(wdlFile, g_nCompressionType, 
								nWDLTotalBlockCount, g_nPageSize);
		g_pDTMPageManager->InitializeCompressionMode(dtmFile, g_nCompressionType, 
								nDTMTotalBlockCount, g_nPageSize);
	}



	 // Compresses SubDb Slice
	////////////////////////////////////////////////////////////////////////////

	template<typename BB, int BitsPerRow, int MaxField>
	int CompressSubDbSlice(int wm, int wk, int bm, int bk, int wmrank, int bmrank, FILE *fp_cpr, FILE *fp_dtm_cpr)
	{
		int symmetric = 0;
		int index;
		int64 memsize; // will not work for databases which require more than 2-4GB ram.
		int loadblack = 0, loadwhite = 0;
		int64 dbsize;
		int64 wdlDbsize;
		int64 dtmDbSize;
		BB p;

		/////////////////////////////////////////////////////////////////////////
		// report progress

		if(OnCompressSubDbStartedPtr != NULL)
		{
			OnCompressSubDbStartedPtr(wm, wk, bm, bk, wmrank, bmrank);
		}


		/////////////////////////////////////////////////////////////////////////
		// check for a valid db - is it's reverse already computed?:
		//
		if(wm==bm && wk==bk)
		{
			if(bmrank>wmrank)
				return 0;
		}

		////////////////////////////////////////////////////////////////////////
		// get the size of our database 

		dbsize = GetDatabaseSize<BitsPerRow, MaxField>(wm, wk, bm, bk, wmrank, bmrank);
		dtmDbSize = dbsize;
		wdlDbsize = Pad16(dbsize / 4);
	
		// check if the database is symmetric
		if(bm==wm && bk==wk && bmrank==wmrank)
			symmetric = 1;
	
		////////////////////////////////////////////////////////////////////////
		// Load Raw Databases, either WDL and DTM
		//

		// load white WDL raw database
		wchar_t dbname[PATH_MAX];
		SetDbName(wm, wk, bm, bk, wmrank, bmrank, WHITE, dbname);
		FILE* fp;

		_wfopen_s(&fp, dbname,L"rb");
		if(fp!=NULL)
		{
			memsize = Pad16(dbsize/4);
			currentdb_w = new uint8[(size_t)memsize];
			fread(currentdb_w, 1, (size_t)memsize, fp);
			fclose(fp);
		}

		// load white DTM dabatase
		SetDbDTMName(wm, wk, bm, bk, wmrank, bmrank, WHITE, dbname);
		_wfopen_s(&fp, dbname,L"rb");
		if(fp!=NULL)
		{
			memsize = dbsize;
			cur_dtm_w = (uint8*)new uint8[(size_t)memsize];
			fread(cur_dtm_w, 1, (size_t)memsize, fp);
			fclose(fp);
		}

		
		// load black, if not symmetric
		if(!symmetric)
			{
			// load black WDL raw database
			SetDbName(wm, wk, bm, bk, wmrank, bmrank, BLACK, dbname);
			_wfopen_s(&fp, dbname,L"rb");
			if(fp!=NULL)
			{
				memsize = Pad16(dbsize/4);
				currentdb_b = new uint8[(size_t)memsize];
				fread(currentdb_b, 1, (size_t)memsize, fp);
				fclose(fp);
			}

			// load black DTM database
			SetDbDTMName(wm, wk, bm, bk, wmrank, bmrank, BLACK, dbname);
			_wfopen_s(&fp, dbname,L"rb");
			if(fp!=NULL)
			{
				memsize = dbsize;
				cur_dtm_b = (uint8*)new uint8[(size_t)memsize];
				fread(cur_dtm_b, 1, (size_t)memsize, fp);
				fclose(fp);
			}
		}

		////////////////////////////////////////////////////////////////////////
		// Optimizations for better compression
		//

		// TO-DO: FOR NOW SWITCHED OFF, ALLOWED AFTER SUCCESSFULLY DEBUGGED COMPRESSION
		// set unknown good BitBoards for runlengthmaximize, optimization for compression
		//if(symmetric)
		//{
		//	for(index=0;index<dbsize;index++)
		//	{
		//		IndexToBitBoardXX<BB>(index, &p, wm, wk, bm, bk, wmrank, bmrank);
		//		// impossible BitBoard
		//		if(p.bm & p.wm)
		//		{
		//			SetDatabaseValueNOMP(currentdb_w, index, UNKNOWN);
		//			SetDTMValueNOMP(cur_dtm_w, index, 0);
		//			continue;
		//		}
		//		// captures are not looked by evaluation function
		//		//if(TestJump<BB>(g_ED, &p, true) == true)
		//		//{
		//		//	SetDatabaseValueNOMP(currentdb_w, index, UNKNOWN);
		//		//	SetDTMValueNOMP(cur_dtm_w, index, 0);
		//		//}
		//	}
		//}
		//else
		//{
		//	for(index=0;index<dbsize;index++)
		//	{
		//		IndexToBitBoardXX<BB>(index, &p, wm, wk, bm, bk, wmrank, bmrank);
		//		if(p.bm & p.wm)
		//		{
		//			SetDatabaseValueNOMP(currentdb_w, index, UNKNOWN);
		//			SetDatabaseValueNOMP(currentdb_b, index, UNKNOWN);
		//			SetDTMValueNOMP(cur_dtm_w, index, 0);
		//			SetDTMValueNOMP(cur_dtm_b, index, 0);
		//			continue;
		//		}

		//		// captures are not looked by evaluation function
	
		//		//if(TestJump<BB>(g_ED, &p, true) == true) 
		//		//{
		//		//	SetDatabaseValueNOMP(currentdb_w, index, UNKNOWN);
		//		//	SetDTMValueNOMP(cur_dtm_w, index, 0);
		//		//}
		//		//if(TestJump<BB>(g_ED, &p, false) == true)
		//		//{
		//		//	SetDatabaseValueNOMP(currentdb_b, index, UNKNOWN);
		//		//	SetDTMValueNOMP(cur_dtm_b, index, 0);
		//		//}
		//	}
		//}

		////////////////////////////////////////////////////////////////////////
		// The main compression cycle - write to the paging manager stream
		//

		// compress WHITE database
		EGTBSubDatabase* pWDLDb = pWDLSubDbs + g_nSubDbIndex;
		EGTBSubDatabase* pDTMDb = pDTMSubDbs + g_nSubDbIndex;
		g_nSubDbIndex++;

		///////////////////////////////////
		// Compress WHITE WDL database
		
		// update WDL database
		pWDLDb->wm = wm;
		pWDLDb->wk = wk;
		pWDLDb->bm = bm;
		pWDLDb->bk = bk;
		pWDLDb->wmrank = wmrank;
		pWDLDb->bmrank = bmrank;
		pWDLDb->color = WHITE;
		pWDLDb->nStartBlock = g_pWDLPageManager->GetCurrentBlockNumber();
		pWDLDb->nStartByteInBlock = g_pWDLPageManager->GetCurrentIndexInBlock();

		// do compression
		for(int i = 0; i < wdlDbsize; i++)
		{
			g_pWDLPageManager->WriteByte(currentdb_w[i]);
		}


		///////////////////////////////////
		// Compress WHITE DTM database

		// update DTM database
		pDTMDb->wm = wm;
		pDTMDb->wk = wk;
		pDTMDb->bm = bm;
		pDTMDb->bk = bk;
		pDTMDb->wmrank = wmrank;
		pDTMDb->bmrank = bmrank;
		pDTMDb->color = WHITE;
		pDTMDb->nStartBlock = g_pDTMPageManager->GetCurrentBlockNumber();
		pDTMDb->nStartByteInBlock = g_pDTMPageManager->GetCurrentIndexInBlock();

		// do compression
		for(int i = 0; i < dtmDbSize; i++)
		{
			g_pDTMPageManager->WriteByte(cur_dtm_w[i]);
		}

		// compress BLACK database
		if(!symmetric)
		{
			pWDLDb = pWDLSubDbs + g_nSubDbIndex;
			pDTMDb = pDTMSubDbs + g_nSubDbIndex;
			g_nSubDbIndex++;

			///////////////////////////////////
			// Compress BLACK WDL database
		
			// update WDL database
			pWDLDb->wm = wm;
			pWDLDb->wk = wk;
			pWDLDb->bm = bm;
			pWDLDb->bk = bk;
			pWDLDb->wmrank = wmrank;
			pWDLDb->bmrank = bmrank;
			pWDLDb->color = BLACK;
			pWDLDb->nStartBlock = g_pWDLPageManager->GetCurrentBlockNumber();
			pWDLDb->nStartByteInBlock = g_pWDLPageManager->GetCurrentIndexInBlock();

			// do compression
			for(int i = 0; i < wdlDbsize; i++)
			{
				g_pWDLPageManager->WriteByte(currentdb_b[i]);
			}


			///////////////////////////////////
			// Compress BLACK DTM database

			// update DTM database
			pDTMDb->wm = wm;
			pDTMDb->wk = wk;
			pDTMDb->bm = bm;
			pDTMDb->bk = bk;
			pDTMDb->wmrank = wmrank;
			pDTMDb->bmrank = bmrank;
			pDTMDb->color = BLACK;
			pDTMDb->nStartBlock = g_pDTMPageManager->GetCurrentBlockNumber();
			pDTMDb->nStartByteInBlock = g_pDTMPageManager->GetCurrentIndexInBlock();

			// do compression
			for(int i = 0; i < dtmDbSize; i++)
			{
				g_pDTMPageManager->WriteByte(cur_dtm_b[i]);
			}
		}

		// free allocated memory
		delete[] currentdb_w;
		delete[] cur_dtm_w;
		if(!symmetric)
		{
			delete[] currentdb_b;
			delete[] cur_dtm_b;
		}
		return 1;
	}


	 // Compresses Sub-database
	////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxRank, int BitsPerRow, int MaxField>
	int CompressSubDb(int wm,int wk,int bm,int bk, FILE *fp_cpr, FILE *fp_dtm_cpr)
	{
		int wmrank, bmrank; // maximal rank of the white and black men
		int n = bm+bk+wm+wk;

		// is this a valid database?
		// one side has no pieces:
		if((bm+bk==0) || (wm+wk==0)) return 0;


		// white has more pieces than black
		if(wm+wk<bm+bk)
			return 0;

		// absurd databases: if maxpieces = 8, we don't do 5-3, 6-2, 7-1 or 6-1 or 5-1 etc.
		// this depends on MAXPIECE of course!
		if(wm+wk>MAXPIECE)
			return 0;

		// more bk than wk in case of equal number of pieces
		if(wk+wm==bk+bm)
		{
			if(bk>wk)
				return 0;
		}

		if(bm==0 && wm==0)
			CompressSubDbSlice<BB, BitsPerRow, MaxField>(wm,wk,bm,bk,0,0, fp_cpr, fp_dtm_cpr);
	
		if(wm!=0 && bm==0)
		{
			for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
				CompressSubDbSlice<BB, BitsPerRow, MaxField>(wm,wk,bm,bk,wmrank,0, fp_cpr, fp_dtm_cpr);
		}
	
		if(bm!=0 && wm==0)
		{
			for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
				CompressSubDbSlice<BB, BitsPerRow, MaxField>(wm,wk,bm,bk,0,bmrank, fp_cpr, fp_dtm_cpr);
		}

		if(wm!=0 && bm!=0)
		{
			for(wmrank=MaxRank-1;wmrank>=((wm-1)/BitsPerRow);wmrank--)
			{
				for(bmrank=MaxRank-1;bmrank>=((bm-1)/BitsPerRow);bmrank--)
					CompressSubDbSlice<BB, BitsPerRow, MaxField>(wm,wk,bm,bk,wmrank,bmrank, fp_cpr, fp_dtm_cpr);
			}
		}

		return 1;
	}


	 // Compresses database
	////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxRank, int BitsPerRow, int MaxField>
	int CompressCprDb(int n)
	{

		int bm=0,bk=0,wm=0,wk=0;
		int div1, div2, div3;
		FILE *fp_cpr, *fp_dtm_cpr;

		// file paths 
		wchar_t filename[PATH_MAX];
		wchar_t filepath[PATH_MAX];
	
		// we build one database for every number of stones.
		// open files for this
		swprintf_s(filename, PATH_MAX, L"db%i.cpr",n);
		AttachCprDirectory(filepath, filename);
	
		// test if exists
		_wfopen_s(&fp_cpr, filepath, L"r");
		if(fp_cpr != NULL)
		{
			fclose(fp_cpr);
			PRINT(L"Skipping compression database piece %d", n);
			return 1;
		}

		// open WDL file for writing
		_wfopen_s(&fp_cpr, filepath, L"wb");

		// open DTM file for writing
		swprintf_s(filename, PATH_MAX,L"db%i.dtm.cpr",n);
		AttachCprDirectory(filepath, filename);
		_wfopen_s(&fp_dtm_cpr, filepath, L"wb");

		// initialize compression for given EGTB database file
		InitDbCompression<BitsPerRow, MaxRank, MaxField>(n, fp_cpr, fp_dtm_cpr);

		// the main iteration routine over all databases
		for(div1=0;div1<=n;div1++)
		{
			for(div2=div1;div2<=n;div2++)
			{
				for(div3=div2;div3<=n;div3++)
				{
					wm=div1;
					wk=div2-div1;
					bm=div3-div2;
					bk=n-div3;
					CompressSubDb<BB, MaxRank, BitsPerRow, MaxField>(wm,wk,bm,bk, fp_cpr, fp_dtm_cpr);
				}
			}
		}

		// finalize compression stream
		g_pWDLPageManager->FinalizeCompression();
		g_pDTMPageManager->FinalizeCompression();

		// finalize compression - save headers
		DoneDbCompression(fp_cpr, fp_dtm_cpr);
	
		// close compressed files
		fclose(fp_cpr);
		fclose(fp_dtm_cpr);

		return 1;
	}

	 // The main compression API function
	////////////////////////////////////////////////////////////////////////////

	template<typename BB, int MaxRank, int BitsPerRow, int MaxField>
	void CompressDatabase(int maxPieces, eCheckersType checkersType, int nCompressionType, uint32 nPageSize)
	{
		// allocate the global movegen configuration
		g_ED = new SEngineData();
		memset(g_ED, 0, sizeof(SEngineData));

		// set bitboard generators
		SetCheckersTypeMoveGen(g_ED, checkersType);

		// set global compression settings
		g_eCompressionCheckersType = checkersType;
		g_nCompressionType = nCompressionType;
		g_nPageSize = nPageSize;	

		// do building
		for(int n=2; n<=maxPieces; n++)
		{
			CompressCprDb<BB, MaxRank, BitsPerRow, MaxField>(n);
		}

		// clean up
		delete g_ED;
		g_ED = NULL;
	}
}
